/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */

package cn.hermit.dubbo.scan;

import cn.hermit.bean.BeanDefinition;
import cn.hermit.bean.BeanEvent;
import cn.hermit.bean.BeanFactory;
import cn.hermit.bean.BeanListener;
import cn.hermit.config.Config;
import cn.hermit.dubbo.DubboConfig;
import cn.hermit.dubbo.DubboProperties;
import cn.hermit.dubbo.docs.DubboRouter;
import cn.hermit.nio.http.server.HttpServer;
import cn.hermit.nio.http.server.HttpServerFactory;
import cn.hermit.scan.ScanFilter;
import cn.hermit.util.StringUtils;
import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author JackGao 高建国
 * @description //TODO 设计说明
 * @date 2/6/2017
 */
public class DubboScanFilter implements ScanFilter, BeanListener {

    private static final Logger logger = Logger.getLogger(DubboScanFilter.class.getName());

    private DubboProperties dubboProperties;

    private DubboRouter dubboRouter;

    private final Set<ServiceConfig<?>> serviceConfigs = new ConcurrentHashSet<ServiceConfig<?>>();

    private final ConcurrentMap<String, ReferenceConfig<?>> referenceConfigs = new ConcurrentHashMap<String, ReferenceConfig<?>>();

    public DubboScanFilter() {

        if (StringUtils.isEmpty(System.getProperty("dubbo.application.logger"))) {
            System.setProperty("dubbo.application.logger", "slf4j");
        }

        dubboProperties = Config.getProperties(DubboProperties.PREFIX, DubboProperties.class);

        dubboRouter = new DubboRouter();
        HttpServer httpServer = null;
        try {
            String port = Config.getProperty(DubboProperties.PREFIX + ".docs.port", "12399");
            httpServer = HttpServerFactory.createServer(Integer.parseInt(port), true);
            httpServer.router(dubboRouter).staticRoot("classpath:static").enableCors(true);
        } catch (IOException e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    @Override
    public boolean filter(ClassLoader classLoader, String resourceName, Path path) throws Throwable {
        if (resourceName.endsWith(".class")) {
            resourceName = resourceName.substring(0, resourceName.length() - 6);
        }
        try {
            for (String configName : dubboProperties.getConfig().keySet()) {
                DubboConfig dubboConfig = dubboProperties.getConfig().get(configName);
                if (resourceName.matches(dubboConfig.getServicePattern())) {
                    String clzName = resourceName.replace('/', '.');
                    Class<?> clz = classLoader.loadClass(clzName);
                    BeanDefinition definition = new BeanDefinition();
                    Class<?> interfaceClass = clz.getInterfaces()[0];
                    String beanName = interfaceClass.getName();
                    definition.setClz(clz);
                    definition.setClzName(interfaceClass.getName());
                    BeanFactory.register(beanName, definition);
                    ServiceConfig serviceConfig = new ServiceConfig();
                    serviceConfig.setInterface(interfaceClass);
                    serviceConfig.setInterface(interfaceClass.getName());
                    serviceConfig.setGroup(dubboConfig.getGroup());
                    serviceConfig.setVersion(dubboConfig.getVersion());
                    Object ref = BeanFactory.getBean(beanName);
                    serviceConfig.setRef(ref);

                    ApplicationConfig applicationConfig = null;
                    if (!StringUtils.isBlank(dubboConfig.getApplication())) {
                        applicationConfig = dubboProperties.getApplication().get(dubboConfig.getApplication());
                    }
                    if (applicationConfig == null) {
                        applicationConfig = dubboProperties.getApplication().get("global");
                    }
                    if (applicationConfig == null) {
                        applicationConfig = new ApplicationConfig();
                        applicationConfig.setName(configName);
                        applicationConfig.setOwner("hermit");
                        dubboProperties.getApplication().put("global", applicationConfig);
                    }

                    serviceConfig.setApplication(applicationConfig);

                    List<RegistryConfig> registries = new ArrayList<>();
                    for (String registry : dubboConfig.getRegistries()) {
                        RegistryConfig registryConfig = dubboProperties.getRegistry().get(registry);
                        if (registryConfig != null) {
                            registries.add(registryConfig);
                        }
                    }
                    if (registries.size() == 0) {
                        RegistryConfig registryConfig = dubboProperties.getRegistry().get("global");
                        if (registryConfig == null) {
                            registryConfig = new RegistryConfig();
                            registryConfig.setAddress("zookeeper://127.0.0.1:2181");
                            dubboProperties.getRegistry().put("global", registryConfig);
                        }
                        registries.add(registryConfig);
                    }
                    serviceConfig.setRegistries(registries);

                    ProviderConfig providerConfig = null;
                    if (!StringUtils.isBlank(dubboConfig.getProvider())) {
                        providerConfig = dubboProperties.getProvider().get(dubboConfig.getProvider());
                    }
                    if (providerConfig == null) {
                        providerConfig = dubboProperties.getProvider().get("global");
                    }

                    if (providerConfig == null) {
                        providerConfig = new ProviderConfig();
                        dubboProperties.getProvider().put("global", providerConfig);
                    }

                    serviceConfig.setProvider(providerConfig);

                    ModuleConfig moduleConfig = null;
                    if (!StringUtils.isBlank(dubboConfig.getModule())) {
                        moduleConfig = dubboProperties.getModule().get(dubboConfig.getModule());
                    }
                    if (moduleConfig == null) {
                        moduleConfig = dubboProperties.getModule().get("global");
                    }
                    if (moduleConfig == null) {
                        moduleConfig = new ModuleConfig();
                        moduleConfig.setName(configName);
                        dubboProperties.getModule().put("global", moduleConfig);
                    }
                    serviceConfig.setModule(moduleConfig);

                    ProtocolConfig protocolConfig = null;
                    if (!StringUtils.isBlank(dubboConfig.getProtocol())) {
                        protocolConfig = dubboProperties.getProtocol().get(dubboConfig.getProtocol());
                    }
                    if (protocolConfig == null) {
                        protocolConfig = dubboProperties.getProtocol().get("global");
                    }
                    if (protocolConfig == null) {
                        protocolConfig = new ProtocolConfig();
                        protocolConfig.setPort(30000);
                        dubboProperties.getProtocol().put("global", protocolConfig);
                    }

                    serviceConfig.setProtocol(protocolConfig);

                    serviceConfigs.add(serviceConfig);
                    serviceConfig.export();

                    ReferenceConfig referenceConfig = getReference(serviceConfig, dubboConfig);
                    try {
                        dubboRouter.appendReferenceConfig(referenceConfig);
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, e.getMessage(), e);
                    }

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    private ReferenceConfig getReference(ServiceConfig<Object> serviceConfig, DubboConfig dubboConfig) {
        String key = serviceConfig.getGroup() + "/" + serviceConfig.getInterface() + ":" + serviceConfig.getVersion();
        ReferenceConfig<?> referenceConfig = referenceConfigs.get(key);
        if (referenceConfig == null) {
            referenceConfig = new ReferenceConfig<Object>();
            referenceConfig.setInterface(serviceConfig.getInterface());
            referenceConfig.setVersion(serviceConfig.getVersion());
            referenceConfig.setGroup(serviceConfig.getGroup());

            ApplicationConfig applicationConfig = serviceConfig.getApplication();
            referenceConfig.setApplication(applicationConfig);

            RegistryConfig registryConfig = serviceConfig.getRegistry();
            referenceConfig.setRegistry(registryConfig);

            ConsumerConfig consumerConfig = null;
            if (!StringUtils.isBlank(dubboConfig.getConsumer())) {
                consumerConfig = dubboProperties.getConsumer().get(dubboConfig.getConsumer());
            }
            if (consumerConfig == null) {
                consumerConfig = dubboProperties.getConsumer().get("global");
            }
            if (consumerConfig == null) {
                consumerConfig = new ConsumerConfig();
                consumerConfig.setRetries(0);
                consumerConfig.setCheck(false);
                dubboProperties.getConsumer().put("global", consumerConfig);
            }
            referenceConfig.setConsumer(consumerConfig);

            ModuleConfig moduleConfig = serviceConfig.getModule();
            referenceConfig.setModule(moduleConfig);

            referenceConfigs.putIfAbsent(key, referenceConfig);
            referenceConfig = referenceConfigs.get(key);
        }

        return referenceConfig;
    }

    @Override
    public void handleEvent(BeanEvent event) {
        if(BeanEvent.Type.BEFORE_FIELD_ASSIGNED != event.getType()) {
            return;
        }
        Field field  = (Field) event.get(BeanEvent.KEY_FIELD);
        Class<?> fieldType = field.getType();

    }
}
